var net = require('net');
var uuid = require('node-uuid');
var idGenerator = uuid.generate ? uuid.generate : uuid.v4;

var descrCmd = '__DESCR';
var resultCmd = '__RESULT';

exports = module.exports = light_rpc;

function light_rpc(wrapper) {
	this.wrapper = wrapper;
	this.description = {};
	this.callbacks = {};

	for (var p in wrapper) {
		this.description[p] = {};
	}

	this.descrStr = command(descrCmd, this.description);
	return this;
}

function command(name, data) {
	var cmd = {
		command: name,
		data: data
	};

	var cmdStr = JSON.stringify(cmd);
	return cmdStr;
}


// client
light_rpc.prototype.connect = function(port, host, callback) {
	var connection = new net.createConnection(port, host);
	var self = this;
	connection.setKeepAlive(true);

	connection.on('connect', function() {
		console.log('connected_to_server');
		console.log("write data:", command(descrCmd).toString('utf8'));
		connection.write(command(descrCmd));
	});

	connection.on('data', function(data) {
		console.log('receive_data', data.toString('utf8'));
		var commands = getComands.call(data);
		commands.forEach(commandsCallback);
	});

	function commandsCallback(cmd) {
		if (cmd.command == resultCmd) {
			if (self.callbacks[cmd.data.id]) {
				self.callbacks[cmd.data.id].apply(this, cmd.data.args);
				delete self.callbacks[cmd.data.id];
			}
		} else if (cmd.command == descrCmd) {
			console.log("construct local obj proxy remote obj");
			console.log("self", self);
			var remoteObj = {};

			for (var cmdName in cmd.data) {
				remoteObj[cmdName] = function() {
					var id = idGenerator();
					var args = [].slice.call(arguments);
					// handle last element
					var lastElement = args.pop();
					if (typeof(lastElement) !== 'function') {
						args.push(lastElement);
					} else {
						self.callbacks[id] = lastElement;
					}

					var newCmd = command(cmdName, {id: id,args: args});
					console.log('local object method:' + cmdName + ", connection to remote and write data");
					console.log("write data:", newCmd.toString('utf8'));
					connection.write(newCmd);
				}
			}
			console.log("now use remoteObj to callback,remoteObj=", remoteObj);
			callback(remoteObj, connection);
		}
	};
};

light_rpc.prototype.listen = function(port) {
	this.getServer();
	this.server.listen(port);
};

light_rpc.prototype.getServer = function() {
	var self = this;

	var server = net.createServer(function(c) {
		var commandsCallback = function(cmd) {
			console.log("cmd=" + cmd.command);
			if (cmd.command == descrCmd) {
				console.log("write data:", self.descrStr.toString('utf8'));
				c.write(self.descrStr);
			} else {
				var args = cmd.data.args;
				args.push(getSendCommandBackFunction(c, cmd.data.id));
				console.log("call wrapper=", self.wrapper);
				self.wrapper[cmd.command].apply({}, args);
			}
		};

		c.on('data', function(data) {
			console.log('receive_data', data.toString('utf8'));
			var commands = getComands.call(data);
			commands.forEach(commandsCallback);

		});
	});

	this.server = server;
	return server;
};

function getSendCommandBackFunction(connection, cmdId) {
	return function() {
		var innerArgs = [].slice.call(arguments);
		// remove last if it is a function callback
		var lastElement = innerArgs.pop();
		if (typeof(lastElement) !== 'function') {
			innerArgs.push(lastElement);
		}

		var resultCommand = command(resultCmd, {
			id: cmdId,
			args: innerArgs
		});
		console.log("write data:", resultCommand.toString('utf8'));
		connection.write(resultCommand);
	};
}

function getComands() {
	console.log("getCommands,this= ", this.toString());
	var commands = [];
	var parsedCmd = JSON.parse(this.toString());
	commands.push(parsedCmd);
	console.log("commands:", commands);
	return commands;
}